/********************************************************
 * Filename: TclAutoIndentStrategy.java
 * Author: LMU
 * Program: TUnit
 * Description: Creates the tcl indenting strategy to be
 * used by the Eclipse tcl editor.    
 * Revision History:
 * 20060418: Initial Revision.
 * *****************************************************/

package edu.lmu.cs.tUnit.editor;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;

import edu.lmu.cs.tUnit.log.TUnitLog;
import edu.lmu.cs.tUnit.plugin.TUnitPlugin;
import edu.lmu.cs.tUnit.preferences.TclPreferencesConstants;

/**
 * Auto indent strategy sensitive to brackets.
 * @author LMU
 *
 */
public class TclAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy
implements IAutoEditStrategy {
	
	/**
	 * Method declared on IAutoIndentStrategy
	 */
	public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
		if (c.length == 0 && c.text != null && endsWithDelimiter(d, c.text))
			smartIndentAfterNewLine(d, c);
		else if ("}".equals(c.text)) { 
			smartInsertAfterBracket(d, c);
		}
	}
	
	/**
	 * Returns whether or not the text ends with one of the given search strings.
	 * @param d
	 * @param txt
	 * @return
	 */
	private boolean endsWithDelimiter(IDocument d, String txt) {
		String[] delimiters = d.getLegalLineDelimiters();
			for (int i = 0; i < delimiters.length; i++) {
				if (txt.endsWith(delimiters[i]))
					return true;
			}
		return false;
	}
	
	/**
	 * Returns the line number of the next bracket after end.
	 * @return the line number of the next matching bracket after end
	 * @param document - the document being parsed
	 * @param line - the line to start searching back from
	 * @param end - the end position to search back from
	 * @param closingBracketIncrease - the number of brackets to skip
	 * @return
	 * @throws BadLocationException
	 */
	protected int findMatchingOpenBracket(IDocument document, int line, 
			int end, int closingBracketIncrease) throws BadLocationException {
		int start = document.getLineOffset(line);
		int brackcount = getBracketCount(document, start, end, false) - closingBracketIncrease;

		// Sums up the brackets counts of each line (closing brackets count negative, 
		// opening positive) until we find a line the brings the count to zero
		while (brackcount < 0) {
			line--;
			if (line < 0) {
				return -1;
			}
			
			start = document.getLineOffset(line);
			end = start + document.getLineLength(line) - 1;
			brackcount += getBracketCount(document, start, end, false);
		}
		return line;
	}
	
	/**
	 * Returns the bracket value of a section of text. Closing brackets have a value of -1 and 
	 * open brackets have a value of 1.
	 * @return the line number of the next matching bracket after end
	 * @param document - the document being parsed
	 * @param start - the start position for the search
	 * @param end - the end position for the search
	 * @param ignoreCloseBrackets - whether or not to ignore closing brackets in the count
	 * @throws BadLocationException
	 */
	private int getBracketCount( IDocument document, int start, int end,
			boolean ignoreCloseBrackets) throws BadLocationException {
		int begin = start;
		int bracketcount = 0;
		while (begin < end) {
			char curr = document.getChar(begin);
			begin++;
				switch (curr) {
					case '#' :
						if (begin < end) {
							begin = getCommentEnd(document, begin + 1, end);
						}
						break;
			        
					case '{' :
						bracketcount++;
						ignoreCloseBrackets = false;
						break;
						
					case '}' :
						if (!ignoreCloseBrackets) {
							bracketcount--;
						}
						break;
						
					case '\"' :
						begin = getStringEnd(document, begin, end, curr);
						break;
					default :
				}
		}
		
		return bracketcount;
	}
	
	/**
	 * Returns the end position a comment starting at pos.
	 * 
	 * @return the end position a comment starting at pos
	 * @param document - the document being parsed
	 * @param position - the start position for the search
	 * @param end - the end position for the search
	 */
	private int getCommentEnd(IDocument document, int position, int end) throws BadLocationException {
		int currentPosition = position;
		while (currentPosition < end) {
			char curr = document.getChar(currentPosition);
			currentPosition++;
			if (curr == '\n') {
				if (currentPosition < end) {
					return currentPosition + 1;
				}
			}
		}
		
		return end;
	}

	/**
	 * Returns the String at line with the leading whitespace removed.
	 * 
	 * @return the String at line with the leading whitespace removed.
	 * @param document - the document being parsed
	 * @param line - the line being searched
	 */
	protected String getIndentOfLine(IDocument document, int line) throws BadLocationException {
		if (line > -1) {
			int start = document.getLineOffset(line);
			int end = start + document.getLineLength(line) - 1;
			int whiteend = findEndOfWhiteSpace(document, start, end);
			return document.get(start, whiteend - start);
		} 
		return ""; 
	}
	
	/**
	 * Returns the position of the character in the document after position.
	 * 
	 * @return the next location of character.
	 * @param document - the document being parsed
	 * @param position - the position to start searching from
	 * @param end - the end of the document
	 * @param character - the character you are trying to match
	 */
	private int getStringEnd(IDocument document, int position, int end, char character)
		throws BadLocationException {
		int currentPosition = position;
		while (currentPosition < end) {
			char currentCharacter = document.getChar(currentPosition);
			if (currentCharacter == character) {
				return currentPosition;
			}
			else currentPosition++;
		}
		return end;
	}

	/**
	 * Sets the indent of a new line based on the command provided in the supplied document.
	 * @param document - the document being parsed
	 * @param command - the command being performed
	 */
	protected void smartIndentAfterNewLine(IDocument document, DocumentCommand command) {
		int docLength = document.getLength();
		IPreferenceStore store = TUnitPlugin.getDefault().getPreferenceStore();	
		if (command.offset == -1 || docLength == 0)
			return;

		try {
			int p = (command.offset == docLength ? command.offset - 1 : command.offset);
			int line = document.getLineOfOffset(p);

			StringBuffer buf = new StringBuffer(command.text);
			if (command.offset < docLength && document.getChar(command.offset) == '}') {
				int indLine = findMatchingOpenBracket(document, line, command.offset, 0);
				
				if (indLine == -1) {
					indLine = line;
				}
				
				buf.append(getIndentOfLine(document, indLine));
			} 
			
			else {
				int start = document.getLineOffset(line);
				int whiteend = findEndOfWhiteSpace(document, start, command.offset);
				buf.append(document.get(start, whiteend - start));
				if (getBracketCount(document, start, command.offset, true) > 0) {
					if(store.getBoolean(TclPreferencesConstants.P_USE_SPACES_FOR_TABS)) {
						for(int i = 0;i < store.getInt(TclPreferencesConstants.P_NUMBERSPACES); i++){
							buf.append(' ');
						}
					}
					else buf.append('\t');
				}
			}
			command.text = buf.toString();

		} catch (BadLocationException e) {
			TUnitLog.logError("Smart Indentation failed.", e);
		}
	}
	
	/**
	 * Sets the indent of a bracket based on the command provided in the supplied document.
	 * 
	 * @param document - the document being parsed
	 * @param command - the command being performed
	 */
	protected void smartInsertAfterBracket(IDocument document, DocumentCommand command) {
		if (command.offset == -1 || document.getLength() == 0)
			return;

		try {
			int p = (command.offset == document.getLength() ? command.offset - 1 : command.offset);
			int line = document.getLineOfOffset(p);
			int start = document.getLineOffset(line);
			int whiteend = findEndOfWhiteSpace(document, start, command.offset);

			// shifts only when line does not contain any text up to the closing bracket
			if (whiteend == command.offset) {
				// evaluate the line with the opening bracket that matches out closing bracket
				int indLine = findMatchingOpenBracket(document, line, command.offset, 1);
				
				if (indLine != -1 && indLine != line) {
					// takes the indent of the found line
					StringBuffer replaceText = new StringBuffer(getIndentOfLine(document, indLine));
					
					// adds the rest of the current line including the just added close bracket
					replaceText.append(document.get(whiteend, command.offset - whiteend));
					replaceText.append(command.text);
					
					// modifies document command
					command.length = command.offset - start;
					command.offset = start;
					command.text = replaceText.toString();
				}
			}
		} catch (BadLocationException e) {
			TUnitLog.logError("Smart Indentation failed.", e);
		}
	}

}